第18天了,今天是禮拜五,我的心不是在下班就是在下班的路上。
狀態模式,直接舉例解釋,我們來實做一個電燈按鈕開關,按一下電燈是開,按一下是關
var Light = function() {
this.state = 'off';
this.button = null;
};
Light.prototype.init = function() {
var self = this;
$('<button>').text('開關').appendTo('body').click(function() {
self.buttonWasPressed();
});
};
Light.prototype.buttonWasPressed = function() {
if (this.state === 'off') {
console.log('開');
this.state = 'on';
} else {
console.log('關');
this.state = 'off';
}
};
var light = new Light();
light.init();
看起來好像沒有什麼問題,那如果電燈功能是不只一種狀態呢?以目前的寫法假設電燈狀態有'弱光'、'強光'與'關登'三種,可能會這麼寫:
Light.prototype.buttonWasPressed = function() {
if (this.state === 'off') {
console.log('弱光');
this.state = 'weakLight';
} else if (this.state === 'weakLight') {
console.log('強光');
this.state = 'strongLight';
} else {
console.log('關燈');
this.state = 'off';
}
};
現在我們來分析一下缺點,首先是當有狀態增加時,都必須要改動buttonWasPressed內部的程式碼,這樣是違反開放-封閉原則。再來是這樣新增狀態下去buttonWasPressed會持續膨脹且if else分支會相當多。
現在我們用狀態模式來修改,一般來說通常的方法都會是封裝'方法',而狀態模式要的是封裝'狀態'
首先先是作關燈的狀態
var OffLightState = function(light) {
this.light = light;
};
//按鈕被按之後要做的事
OffLightState.prototype.buttonWasPressed = function() {
console.log('弱光');
this.light.setState(this.light.weakLightState); //切換到弱光的狀態
};
再來是作弱光的狀態
var WeakLightState = function(light) {
this.light = light;
};
WeakLightState.prototype.buttonWasPressed = function() {
console.log('強光');
this.light.setState(this.light.strongLightState); //切換到強光的狀態
};
最後是關燈的狀態
var StrongLightState = function(light) {
this.light = light;
};
StrongLightState.prototype.buttonWasPressed = function() {
console.log('關燈');
this.light.setState(this.light.offLightState); //切換到關燈的狀態
};
現在我們來修改Light模組,我們不再用字處當作狀態,而是使用我們上面作的這些物件當作狀態
var Light = function() {
this.offLightState = new OffLightState(this);
this.weakLightState = new WeakLightState(this);
this.strongLightState = new StrongLightState(this);
this.button = null;
};
Light.prototype.init = function() {
var self = this;
this.current = this.offLightState; //設置初始狀態
$('<button>').text('開關').appendTo('body').click(function() {
self.current.buttonWasPressed();
});
};
再來做設定狀態setState的功能,其實就是利用init裡面做的current狀態,把新的狀態指定到這個屬性上
Light.prototype.setState = function(newState) {
this.current = newState;
};
實際用一下
var light = new Light();
light.init();
理論上結果跟原本的寫法是一樣的,這種寫法當有新的狀態要加入時,只要增加一個狀態類別,並且改一下前後關聯的狀態就好。
這時候我們再來看狀態模式的定義:允許一個物件在其內部狀態改變時改變它的行為,物件看起來似乎修改了它的類別。在這裡我們將Light物件的內部狀態(弱光、強光、關燈)封裝成獨立的類別,所以當物件內部狀態改變時,會有不同的效果。而對於這個物件的使用者而言,看起來是用了不同的類別來實作的。